package decode

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/des"
	"encoding/base64"
	"encoding/hex"
	"errors"
	"reflect"
	"unsafe"

	log "github.com/sirupsen/logrus"
)

type cfb8 struct {
	b         cipher.Block
	blockSize int
	in        []byte
	out       []byte

	decrypt bool
}

func (x *cfb8) XORKeyStream(dst, src []byte) {
	for i := range src {
		x.b.Encrypt(x.out, x.in)
		copy(x.in[:x.blockSize-1], x.in[1:])
		if x.decrypt {
			x.in[x.blockSize-1] = src[i]
		}
		dst[i] = src[i] ^ x.out[0]
		if !x.decrypt {
			x.in[x.blockSize-1] = dst[i]
		}
	}
}

func newCFB8Encrypter(block cipher.Block, iv []byte) cipher.Stream {
	return newCFB8(block, iv, false)
}

func newCFB8Decrypter(block cipher.Block, iv []byte) cipher.Stream {
	return newCFB8(block, iv, true)
}

func newCFB8(block cipher.Block, iv []byte, decrypt bool) cipher.Stream {
	blockSize := block.BlockSize()
	if len(iv) != blockSize {
		// stack trace will indicate whether it was de or encryption
		panic("cipher.newCFB: IV length must equal block size")
	}
	x := &cfb8{
		b:         block,
		blockSize: blockSize,
		out:       make([]byte, blockSize),
		in:        make([]byte, blockSize),
		decrypt:   decrypt,
	}
	copy(x.in, iv)

	return x
}

func DecodeCFB8(encrypt_data string, key string) (origin_data string, err error) {

	var source []byte
	if source, err = base64.StdEncoding.DecodeString(encrypt_data); err != nil {
		log.Errorf("decode base64 error: %v", err)
		return
	}

	iv := source[:16]
	var block cipher.Block
	if block, err = aes.NewCipher([]byte(key)); err != nil {
		log.Errorf("new cipher error: %v", err)
		return
	}
	//go default  segment size is 128,python default is 8
	// encrypt := cipher.NewCFBDecrypter(block, iv)
	encrypt := newCFB8Decrypter(block, iv)

	var dst []byte = make([]byte, len(source))
	encrypt.XORKeyStream(dst, source)
	origin_data = string(dst[16:])
	return
}

func DecodeCFB128(encrypt_data string, key string) (origin_data string, err error) {

	var source []byte
	if source, err = base64.StdEncoding.DecodeString(encrypt_data); err != nil {
		log.Errorf("decode base64 error: %v", err)
		return
	}

	iv := source[:16]
	var block cipher.Block
	if block, err = aes.NewCipher([]byte(key)); err != nil {
		log.Errorf("new cipher error: %v", err)
		return
	}
	encrypt := cipher.NewCFBDecrypter(block, iv)

	var dst []byte = make([]byte, len(source))
	encrypt.XORKeyStream(dst, source[16:])
	origin_data = string(dst)
	return
}

func pkCS5Unpadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}

func String2Bytes(s string) []byte {
	sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
	bh := reflect.SliceHeader{
		Data: sh.Data,
		Len:  sh.Len,
		Cap:  sh.Len,
	}
	return *(*[]byte)(unsafe.Pointer(&bh))
}

func Bytes2String(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}

func makeBuffer(le int) []byte {
	return make([]byte, le)
}

func DecodeCBC(encrypt_data string, key string) (origin_data string, err error) {

	defer func() {
		if err := recover(); err != nil {
			log.Errorf("decode error: %v", err)
		}
	}()
	var source []byte
	if encrypt_data[len(encrypt_data)-1] == '\n' {
		encrypt_data = encrypt_data[:len(encrypt_data)-1]
	}
	if source, err = base64.StdEncoding.DecodeString(encrypt_data); err != nil {
		log.Errorf("decode base64 error: %v", err)
		return
	}

	iv := source[:16]
	var block cipher.Block
	if block, err = aes.NewCipher([]byte(key)); err != nil {
		log.Errorf("new cipher error: %v", err)
		return
	}
	encrypt := cipher.NewCBCDecrypter(block, iv)
	le := len(source)
	var dst []byte = makeBuffer(le)
	// var dst []byte = make([]byte, len(source))
	encrypt.CryptBlocks(dst, source)
	origin_data = Bytes2String(pkCS5Unpadding(dst[16:]))
	return
}

var code1 = []byte{54, 99, 56, 55, 102, 51, 55, 102, 57, 41, 101, 53, 56, 102, 98, 53, 52, 97, 57, 55, 100, 99, 48, 98, 48, 101, 51, 48, 55, 102, 48, 56, 97, 101, 53, 102, 98, 51, 98, 50, 101, 48, 53, 53, 99, 102, 50, 50}
var code2 = []byte{33, 64, 37, 35, 37, 37, 35, 38}

func newCoderStr() []byte {
	code2[1] += 10
	code2[3] -= 10
	return code2
}

func zeroUnPadding(origData []byte) []byte {
	return bytes.TrimFunc(origData,
		func(r rune) bool {
			return r == rune(0)
		})
}

func GetDefaultMixStr() (string, error) {
	decrypted := code1
	defer func() {
		if err := recover(); err != nil {

		}
	}()
	coderByte := newCoderStr()
	decrypted[1] += 1
	decrypted[9] += 10
	src, err := hex.DecodeString(string(decrypted))
	if err != nil {
		return "", err
	}

	block, err := des.NewCipher(coderByte)
	if err != nil {
		return "", err
	}
	out := make([]byte, len(src))
	dst := out
	bs := block.BlockSize()
	if len(src)%bs != 0 {
		return "", errors.New("crypto/cipher: input not full blocks")
	}
	for len(src) > 0 {
		block.Decrypt(dst, src[:bs])
		src = src[bs:]
		dst = dst[bs:]
	}
	out = zeroUnPadding(out)

	return string(out), nil
}
